# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
#
# This library is free software: you can redistribute it and/or modify it 
# under the terms of the GNU Lesser General Public License as published by 
# the Free Software Foundation, either version 3 of the License, or 
# (at your option) any later version. This library is distributed in the 
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the 
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
# See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.

import os
import logging

import FXstudiolibrary

from FXstudiovendor.Qt import QtGui
from FXstudiovendor.Qt import QtCore
from FXstudiovendor.Qt import QtWidgets

from FXstudiolibrarymaya import baseitem
from FXstudiolibrarymaya import baseloadwidget

try:
	import FXmutils
	import maya.cmds
	import maya.cmds as cmds
except ImportError as error:
	print(error)


logger = logging.getLogger(__name__)


def save(path, *args, **kwargs):
	"""Convenience function for saving a PoseItem."""
	PoseItem(path).safeSave(*args, **kwargs)


def load(path, *args, **kwargs):
	"""Convenience function for loading a PoseItem."""
	PoseItem(path).load(*args, **kwargs)


class PoseLoadWidget(baseloadwidget.BaseLoadWidget):

	@classmethod
	def createFromPath(cls, path, theme=None):

		item = PoseItem(path)
		widget = cls(item)

		if not theme:
			import FXstudiolibrary.widgets
			theme = FXstudiolibrary.widgets.Theme()
			widget.setStyleSheet(theme.styleSheet())

		widget.show()

	def __init__(self, *args, **kwargs):
		super(PoseLoadWidget, self).__init__(*args, **kwargs)

		self._options = None
		self._pose = FXmutils.Pose.fromPath(self.item().transferPath())

		self.ui.blendFrame = QtWidgets.QFrame(self)

		layout = QtWidgets.QHBoxLayout(self)
		self.ui.blendFrame.setLayout(layout)

		if self.item().libraryWindow():
			self.item().libraryWindow().itemsWidget().itemSliderMoved.connect(self._sliderMoved)
			self.item().libraryWindow().itemsWidget().itemSliderReleased.connect(self._sliderReleased)
			self.item().libraryWindow().itemsWidget().itemDoubleClicked.connect(self._itemDoubleClicked)

		self.ui.blendSlider = QtWidgets.QSlider(self)
		self.ui.blendSlider.setObjectName("blendSlider")
		self.ui.blendSlider.setMinimum(-30)
		self.ui.blendSlider.setMaximum(130)
		self.ui.blendSlider.setOrientation(QtCore.Qt.Horizontal)
		self.ui.blendSlider.sliderMoved.connect(self._sliderMoved)
		self.ui.blendSlider.sliderReleased.connect(self._sliderReleased)

		self.ui.blendEdit = QtWidgets.QLineEdit(self)
		self.ui.blendEdit.setObjectName("blendEdit")
		self.ui.blendEdit.setText("0")
		self.ui.blendEdit.editingFinished.connect(self._blendEditChanged)

		validator = QtGui.QIntValidator(-200, 200, self)
		self.ui.blendEdit.setValidator(validator)

		layout.addWidget(self.ui.blendSlider)
		layout.addWidget(self.ui.blendEdit)

		self.setCustomWidget(self.ui.blendFrame)

	def _itemDoubleClicked(self):
		"""Triggered when the user double-clicks a pose."""
		self.accept()

	def _sliderMoved(self, value):
		"""Triggered when the user moves the slider handle."""
		self.load(blend=value, batchMode=True)

	def _sliderReleased(self):
		"""Triggered when the user releases the slider handle."""
		try:
			self.load(blend=self.ui.blendSlider.value(), refresh=False)
			self._options = {}
		except Exception as error:
			self.item().showErrorDialog("Item Error", str(error))
			raise

	def _blendEditChanged(self, *args):
		"""Triggered when the user changes the blend edit value."""
		try:
			self._options = {}
			self.load(blend=int(self.ui.blendEdit.text()), clearSelection=False)
		except Exception as error:
			self.item().showErrorDialog("Item Error", str(error))
			raise

	def loadValidator(self, *args, **kwargs):
		self._options = {}
		return super(PoseLoadWidget, self).loadValidator(*args, **kwargs)

	def accept(self):
		"""Triggered when the user clicks the apply button."""
		try:
			self._options = {}
			self.load(clearCache=True,  clearSelection=False)
		except Exception as error:
			self.item().showErrorDialog("Item Error", str(error))
			raise

	def load(
		self,
		blend=100.0,
		refresh=True,
		batchMode=False,
		clearCache=False,
		clearSelection=True,
	):
		"""
		Load the pose item with the current user settings from disc.

		:type blend: float
		:type refresh: bool
		:type batchMode: bool
		:type clearCache: bool
		:type clearSelection: bool
		"""
		# Select the curve where you want to get the point
		curve = cmds.curve(p =[(0,0,12),(0,0,10.666667),(0,0,8),(0,0,4),(0,0,0),(0,0,-4),(0,0,-8),(0,0,-10.666667),(0,0,-12)] ,k=[0,0,0,1,2,3,4,5,6,6,6])

		curveshapes = cmds.listRelatives(curve, shapes=True)
		cmds.setAttr (curveshapes[0] + ".overrideEnabled", 1)
		cmds.setAttr (curveshapes[0] + ".overrideDisplayType", 2)
		# Gets the names of all points on the curve
		point_list = cmds.ls(curve + '.cv[*]', flatten=True)
		waveGrp =cmds.createNode( "transform", n ="waveGrp")  
		Cv_Grp =cmds.createNode( "transform", n ="Cv_Grp") 
		clu_Grp =cmds.createNode( "transform", n ="clu_Grp") 
		# Walk through each point and get location information
		for point in range(len(point_list)):
			point_Name =  point_list[point]
			# Get the location information of the point
			position = cmds.xform(point_Name, query=True, translation=True, worldSpace=True)
			clu = cmds.cluster(  point_Name,rel=True ,name=curve + str(point))
			wave_Cv =cmds.circle (n = curve + "_Cv" + str(point) ,c=[ 0 ,0 ,0],nr =[0 ,0,1] )[0]
			cmds.xform(wave_Cv, r= 1,translation=position)
			cmds.parentConstraint (wave_Cv, clu[1],mo =1,weight=1 )
			cmds.scaleConstraint(wave_Cv, clu[1],mo =1,weight=1 )
			cmds.setAttr (clu_Grp+".hiddenInOutliner", 1)
			cmds.setAttr (clu_Grp+".visibility", 0)
			
			cmds.parent (wave_Cv, Cv_Grp )  
			cmds.parent (clu, clu_Grp )

		circle_min = cmds.circle(name="circle_min",c=[ 0 ,0 ,0],nr =[0 ,1,0],radius=15)
		cmds.parentConstraint (circle_min, Cv_Grp,mo =1,weight=1 )
		cmds.scaleConstraint(circle_min, Cv_Grp,mo =1,weight=1 )
		mod = cmds.polyCylinder (r =1,h =5 ,sx= 20 ,sy= 1 ,sz=1 ,ax= (0 ,0 ,1),rcp= 0 ,cuv= 3 ,ch =1)

		#cmds.setAttr (curve+".hiddenInOutliner", 1)
			
		cmds.parent (curve,circle_min ,clu_Grp,mod,Cv_Grp , waveGrp )

		modshapes = cmds.listRelatives(mod[0], shapes=True)
		Deformer = cmds.deformer (modshapes,type ="curveWarp" ,n = mod[0] + "curveWarp")
		cmds.connectAttr(curveshapes[0] + ".worldSpace[0]" ,Deformer[0]+".inputCurve")

		cmds.addAttr(circle_min[0],ln ="offset",at ="double",min = 0 , max = 100,dv=0)
		cmds.addAttr(circle_min[0],ln ="lengthScale",at ="double",min = 0 ,dv=100)
		cmds.addAttr(circle_min[0],ln ="maxScale",at ="double",min =0,dv=1)
		cmds.addAttr(circle_min[0],ln ="height",at ="double",min = 3,dv=3)
		cmds.addAttr(circle_min[0],ln ="subdivisionsHeight",at ="double",min = 10 ,dv=10)

		cmds.setAttr (circle_min[0] + ".offset",e =1,keyable =1 )
		cmds.setAttr (circle_min[0] + ".lengthScale",e =1,keyable =1 )
		cmds.setAttr (circle_min[0] + ".maxScale",e =1,keyable =1 )
		cmds.setAttr (circle_min[0] + ".height",e =1,keyable =1 )
		cmds.setAttr (circle_min[0] + ".subdivisionsHeight",e =1,keyable =1 )


		ShadingNode =cmds.shadingNode ("multiplyDivide",asUtility= 1,name = mod[0]+"multiplyDivide")
		cmds.connectAttr(circle_min[0]+".offset", ShadingNode+".input1X",f=1)
		cmds.connectAttr(ShadingNode+".outputX", Deformer[0]+".offset",f=1)
		cmds.setAttr (ShadingNode+".input2X",0.01)

		#lengthScale_ShadingNode =cmds.shadingNode ("multiplyDivide",asUtility= 1,name = mod[0]+"lengthScaleMultiplyDivide")
		cmds.connectAttr(circle_min[0]+".lengthScale", ShadingNode+".input1Y",f=1)
		#cmds.connectAttr(ShadingNode+".outputX", Deformer[0]+".offset",f=1)
		cmds.connectAttr(ShadingNode+".outputY", Deformer[0]+".lengthScale",f=1)
		cmds.setAttr (ShadingNode+".input2Y",0.01)


		cmds.connectAttr(circle_min[0]+".maxScale", Deformer[0]+".maxScale",f=1)
		cmds.connectAttr(circle_min[0]+".height", mod[1]+".height",f=1)
		cmds.connectAttr(circle_min[0]+".subdivisionsHeight",  mod[1]+".subdivisionsHeight",f=1)
		cmds.select( circle_min,r=1 )

		"""
		if batchMode:
			self.formWidget().setValidatorEnabled(False)
		else:
			self.formWidget().setValidatorEnabled(True)

		if not self._options:

			self._options = self.formWidget().values()
			self._options['mirrorTable'] = self.item().mirrorTable()
			self._options['objects'] = maya.cmds.ls(selection=True) or []

			if not self._options["searchAndReplaceEnabled"]:
				self._options["searchAndReplace"] = None

			del self._options["namespaceOption"]
			del self._options["searchAndReplaceEnabled"]

		self.ui.blendEdit.blockSignals(True)
		self.ui.blendSlider.setValue(blend)
		self.ui.blendEdit.setText(str(int(blend)))
		self.ui.blendEdit.blockSignals(False)

		if self.item().libraryWindow():

			self.item().libraryWindow().itemsWidget().blockSignals(True)
			self.item().setSliderValue(blend)
			self.item().libraryWindow().itemsWidget().blockSignals(False)

			if batchMode:
				self.item().libraryWindow().showToastMessage("Blend: {0}%".format(blend))

		try:
			self._pose.load(
				blend=blend,
				refresh=refresh,
				batchMode=batchMode,
				clearCache=clearCache,
				clearSelection=clearSelection,
				**self._options
			)
		finally:
			self.item().setSliderDown(batchMode)
		"""


def findMirrorTable(path):
	"""
	Get the mirror table object for this item.

	:rtype: FXmutils.MirrorTable or None
	"""
	mirrorTable = None

	mirrorTablePaths = list(FXstudiolibrary.walkup(
			path,
			match=lambda path: path.endswith(".mirror"),
			depth=10,
		)
	)

	if mirrorTablePaths:
		mirrorTablePath = mirrorTablePaths[0]

		path = os.path.join(mirrorTablePath, "mirrortable.json")

		if path:
			mirrorTable = FXmutils.MirrorTable.fromPath(path)

	return mirrorTable


class PoseItem(baseitem.BaseItem):

	NAME = "Pose"
	EXTENSION = ".pose"
	ICON_PATH = os.path.join(os.path.dirname(__file__), "icons", "pose.png")
	LOAD_WIDGET_CLASS = PoseLoadWidget
	TRANSFER_CLASS = FXmutils.Pose
	TRANSFER_BASENAME = "pose.json"

	def __init__(self, *args, **kwargs):
		"""
		Create a new instance of the pose item from the given path.

		:type path: str
		:type args: list
		:type kwargs: dict
		"""
		super(PoseItem, self).__init__(*args, **kwargs)

		self.setSliderEnabled(True)

	def mirrorTableSearchAndReplace(self):
		"""
		Get the values for search and replace from the mirror table.

		:rtype: (str, str)
		"""
		mirrorTable = findMirrorTable(self.path())

		return mirrorTable.leftSide(), mirrorTable.rightSide()

	def switchSearchAndReplace(self):
		"""
		Switch the values of the search and replace field.

		:rtype: (str, str)
		"""
		values = self.currentLoadValue("searchAndReplace")
		return values[1], values[0]

	def clearSearchAndReplace(self):
		"""
		Clear the search and replace field.

		:rtype: (str, str)
		"""
		return '', ''

	def doubleClicked(self):
		"""Triggered when the user double-click the item."""
		pass

	def loadSchema(self):
		"""
		Get schema used to load the pose item.

		:rtype: list[dict]
		"""
		schema = [
			{
				"name": "optionsGroup",
				"title": "Options",
				"type": "group",
				"order": 2,
			},
			{
				"name": "key",
				"type": "bool",
				"inline": True,
				"default": False,
				"persistent": True,
			},
			{
				"name": "mirror",
				"type": "bool",
				"inline": True,
				"default": False,
				"persistent": True,
			},
			{
				"name": "additive",
				"type": "bool",
				"inline": True,
				"default": False,
				"persistent": True,
			},
			{
				"name": "searchAndReplaceEnabled",
				"title": "Search and Replace",
				"type": "bool",
				"inline": True,
				"default": False,
				"persistent": True,
			},
			{
				"name": "searchAndReplace",
				"title": "",
				"type": "stringDouble",
				"default": ("", ""),
				"placeholder": ("search", "replace"),
				"persistent": True,
				"actions": [
					{
						"name": "Switch",
						"callback": self.switchSearchAndReplace,
					},
					{
						"name": "Clear",
						"callback": self.clearSearchAndReplace,
					},
					{
						"name": "From Mirror Table",
						"enabled": bool(findMirrorTable(self.path())),
						"callback": self.mirrorTableSearchAndReplace,
					},
				]
			},
		]

		schema.extend(super(PoseItem, self).loadSchema())

		return schema
	
	def mirrorTable(self):
		return findMirrorTable(self.path())

	def loadValidator(self, **values):
		"""
		Using the validator to change the state of the mirror option.

		:type values: dict
		:rtype: list[dict]
		"""
		# Mirror check box
		mirrorTip = "Cannot find a mirror table!"
		mirrorTable = findMirrorTable(self.path())
		if mirrorTable:
			mirrorTip = "Using mirror table: %s" % mirrorTable.path()

		fields = [
			{
				"name": "mirror",
				"toolTip": mirrorTip,
				"enabled": mirrorTable is not None,
			},
			{
				"name": "searchAndReplace",
				"visible": values.get("searchAndReplaceEnabled")
			},
		]

		fields.extend(super(PoseItem, self).loadValidator(**values))

		return fields

	def save(self, objects, **kwargs):
		"""
		Save all the given object data to the item path on disc.

		:type objects: list[str]
		:type kwargs: dict
		"""
		super(PoseItem, self).save(**kwargs)

		# Save the pose to the temp location
		FXmutils.savePose(
			self.transferPath(),
			objects,
			metadata={"description": kwargs.get("comment", "")}
		)
